﻿//****************************************************************************
// Copyright (c) 2010 All Right Reserved, http://www.sublight.si/
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from
// the use of this software.
//
// The origin of this software must not be misrepresented; you must not claim
// that you wrote the original software. If you use this software in a product,
// an acknowledgment in the product documentation is required.
//
// Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// The source code of the program may be copied and used for educational,
// non-commercial purposes. The source code may be modified if desired,
// to suit the user.
//
// This notice may not be removed or altered from any source distribution.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using NConsoler;
using Sublight.Lib.SubtitlesAPI;
using Utility.Video;

namespace SublightCmd
{
    partial class Program
    {
        private static LogLevel _applicationLogLevel;
        private static SearchHistory _searchHistory = new SearchHistory();

        [Action("automatically download subtitle(s) for multiple video files (same as Batch)")]
        public static void DownloadBatch(
            [Required(Description = "Input directory")]
            string directory,
            [Required(Description = "File type filter")]
            string fileType,
            [Required(Description = "Subtitle languages")]
            string[] languages,
            [Optional(true, Description = "Scan recursive")]
            bool recursive,
            [Optional("", Description = "MaxDownloads|MaxRate|MinReports|Newest")]
            string priority,
            [Optional("", Description = "Sublight username for login.")]
            string username,
            [Optional("", Description = "Sublight password for login")]
            string password,
            [Optional(false, Description = "When searching subtitles for single language, language code is by default not appended.")]
            bool alwaysAppendLanguageCode,
            [Optional("", Description = "Subtitle encoding (utf-8, iso-8859-2, ... See also http://msdn.microsoft.com/en-us/library/system.text.encoding.aspx)")]
            string encoding,
            [Optional(true, Description = "Skip movies which already have downloaded subtitles.")]
            bool smartDownload,
            [Optional("", Description = "Logs information to file.")]
            string logPath,
            [Optional("", Description = "Log level (Debug | Info | Tip | Download | Warning | Error)")]
            string logLevel,
            [Optional(false, Description = "Looks into 'languages' parameter and downloads only one language.")]
            bool reduceLanguageDownloads,
            [Optional(true, Description = "Do linked subtitles have priority when search returns linked and unlinked subtitles.")]
            bool linkedSubtitlesHavePriority,
            [Optional(false, Description = "Does application need user input to exit.")]
            bool waitForExit,
            [Optional(false, Description = "Does application try to guess title if movie is not linked.")]
            bool guessTitle
            )
        {
            Guid session = Guid.Empty;

            const LogLevel defaultLogLevel = LogLevel.Warning; //by default write warnings and errors
            int totalSecondsPaused = 0;

            try
            {
                if (string.IsNullOrEmpty(logLevel))
                {
                    _applicationLogLevel = defaultLogLevel;
                }
                else
                {
                    _applicationLogLevel = (LogLevel)Enum.Parse(typeof(LogLevel), logLevel);
                }
            }
            catch
            {
                _applicationLogLevel = defaultLogLevel;
            }

            try
            {
                Console.Clear();
                Console.WriteLine();
                int bufferWidth = Console.BufferWidth;
                Console.Clear();

                if (!string.IsNullOrEmpty(logPath))
                {
                    //evaluate variables log_{yyyyMMdd}.txt
                    var matches = Regex.Matches(logPath, @"{(?<expr>\D+?)}");
                    foreach (Match match in matches)
                    {
                        if (match.Success)
                        {
                            string expr = match.Groups["expr"].Value;
                            if (!string.IsNullOrEmpty(expr))
                            {
                                try
                                {
                                    string exprVal = DateTime.Now.ToString(expr, System.Globalization.CultureInfo.InvariantCulture);
                                    logPath = logPath.Replace(string.Format("{{{0}}}", expr), exprVal);
                                }
                                catch
                                {
                                }
                            }
                        }
                    }
                }

                if (IsRunningOnMono)
                {
                    Console.BackgroundColor = ConsoleColor.Black;
                    Console.ForegroundColor = ConsoleColor.White;
                }

                var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
                string strVersion = String.Format("Welcome to SublightCmd {0}.{1}.{2}", version.Major, version.Minor, version.Build);
                if (IsRunningOnMono)
                {
                    strVersion += string.Format(" ({0})", Environment.OSVersion.Platform);
                }
                Console.WriteLine(strVersion.PadRight(bufferWidth));
                if (IsRunningOnMono)
                {
                    Console.WriteLine("".PadRight(bufferWidth));
                }

                if (!string.IsNullOrEmpty(username))
                {
                    if (string.IsNullOrEmpty(password) || password.Trim().Length == 0)
                    {
                        Console.Write(string.Format("Please enter password for Sublight username '{0}': ", username));
                        ConsoleColor foreColor = Console.ForegroundColor;
                        Console.ForegroundColor = Console.BackgroundColor;

                        password = Console.ReadLine();

                        Console.ForegroundColor = foreColor;
                        if (!string.IsNullOrEmpty(password))
                        {
                            Console.WriteLine(new string('*', password.Length));
                        }
                    }
                    session = Sublight.Lib.Session.Util.LogIn(username, password);
                }
                else
                {
                    throw new Exception("Missing parameter: username");
                }

                WriteLog(LogLevel.Info, logPath, string.Format("Starting new session for user '{0}' ({1})", username, DateTime.Now));
                string[] cmdArgs = Environment.GetCommandLineArgs();
                if (cmdArgs != null && cmdArgs.Length > 0)
                {
                    StringBuilder sbCmdArgs = new StringBuilder();
                    foreach (string cmdArg in cmdArgs)
                    {
                        if (cmdArg.ToLower().Contains("/password"))
                        {
                            sbCmdArgs.AppendFormat("{0} ", "/password:*****");
                            continue;
                        }
                        sbCmdArgs.AppendFormat("{0} ", cmdArg);
                    }
                    WriteLog(LogLevel.Debug, logPath, string.Format("Command arguments: {0}", sbCmdArgs.ToString().Trim()));
                }
                else
                {
                    WriteLog(LogLevel.Debug, logPath, "Command arguments not available");
                }


                var lstFiles = new List<string>();
                if (fileType != null)
                {
                    string[] fileTypes = fileType.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string ft in fileTypes)
                    {
                        string[] ftTmp = Directory.GetFiles(directory, ft, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
                        if (ftTmp != null && ftTmp.Length > 0)
                        {
                            lstFiles.AddRange(ftTmp);
                        }
                    }
                }
                string[] files = lstFiles.ToArray();

                int left = Console.CursorLeft;
                int top = Console.CursorTop + 1;
                int maxLineLength = 0;

                int totalSubtitles = 0;
                var subtitleExtensions = new[] { "srt", "sub", "sami" };

                bool showTipUseGuessTitleSwitch = false;
                for (int i = 0; i < files.Length; i++)
                {
                    if (i > 0)
                    {
                        top += 5;
                    }

                    string file = files[i];

                    var fi = new FileInfo(file);

                    int pos = i + 1;

                    Console.SetCursorPosition(left, top);
                    string line = string.Format("File {0} / {1}: {2}", pos, files.Length, fi.Name);
                    if (line.Length > maxLineLength)
                    {
                        line = line.PadRight(maxLineLength);
                        maxLineLength = line.Length;
                    }
                    else
                    {
                        line = line.PadRight(maxLineLength); 
                    }
                    Console.Write(line.PadRight(Console.BufferWidth));

                    if (smartDownload)
                    {
                        //did we already download those subtitles once?
                        string fileName = Path.GetFileNameWithoutExtension(file);
                        string dir = Path.GetDirectoryName(file);
                        bool skipSubtitleDownload = false;
                        foreach (string subtitleExtension in subtitleExtensions)
                        {
                            string[] existingSubtitles = Directory.GetFiles(dir, string.Format("{0}.*{1}", fileName, subtitleExtension));
                            if (existingSubtitles != null && existingSubtitles.Length > 0)
                            {
                                skipSubtitleDownload = true;
                                break;
                            }
                        }

                        if (skipSubtitleDownload)
                        {
                            WriteLog(LogLevel.Debug, logPath, string.Format("smartDownload: skipping subtitle download for '{0}' because it already exists", file));
                            RefreshUI(left, top, Status.Skipped, Status.Skipped, Status.Skipped, totalSubtitles);
                            continue;
                        }
                    }

                    Status resultHashing;
                    Status resultSearching;
                    Status resultDownloading;

                    if (MovieRecentlySearched(file))
                    {
                        //skip subtitle search for this movie because it was already searched recently
                        WriteLog(LogLevel.Debug, logPath, string.Format("skipping subtitle search for '{0}' because it was already searched recently", file));

                        resultHashing = Status.Skipped;
                        resultSearching = Status.Skipped;
                        resultDownloading = Status.Skipped;
                        RefreshUI(left, top, resultHashing, resultSearching, resultDownloading, totalSubtitles);
                        continue;
                    }

                    //hashing
                    resultHashing = Status.InProgress;
                    resultSearching = Status.Waiting;
                    resultDownloading = Status.Waiting;

                    RefreshUI(left, top, resultHashing, resultSearching, resultDownloading, totalSubtitles);
                    string hash = Checksum.Compute(file);

                    //searching
                    resultHashing = Status.Success;
                    resultSearching = Status.InProgress;
                    RefreshUI(left, top, resultHashing, resultSearching, resultDownloading, totalSubtitles);
                    Subtitle[] subtitles;
                    try
                    {
                        string title = null;
                        byte? season = null;
                        int? episode = null;

                        if (guessTitle)
                        {
                            if (!DetectTitleSeasonEpisode(file, out title, out season, out episode))
                            {
                                title = null;
                                season = null;
                                episode = null;
                            }
                        }

                        WriteLog(LogLevel.Debug, logPath, string.Format("Searching subtitles by hash (id = {0})...", hash));
                        subtitles = Sublight.Lib.Subtitle.Util.SearchByHash(session, hash, title, season, episode, languages, priority, linkedSubtitlesHavePriority);
                    }
                    catch (Sublight.Lib.Types.Exception.QuotaReached)
                    {
                        resultSearching = Status.Failure;
                        RefreshUI(left, top, resultHashing, resultSearching, resultDownloading, totalSubtitles);
                        throw;

                    }
                    catch (Exception ex)
                    {
                        WriteLog(LogLevel.Error, logPath, string.Format("SearchByHash: {0}", ex.Message));

                        resultSearching = Status.Failure;
                        RefreshUI(left, top, resultHashing, resultSearching, resultDownloading, totalSubtitles);
                        Thread.Sleep(1000);
                        continue;
                    }

                    if (subtitles == null || subtitles.Length <= 0)
                    {
                        if (!guessTitle)
                        {
                            showTipUseGuessTitleSwitch = true;
                        }
                        resultSearching = Status.SubtitleNotFound;
                        resultDownloading = Status.Skipped;
                        RefreshUI(left, top, resultHashing, resultSearching, resultDownloading, totalSubtitles);
                        WriteLog(LogLevel.Tip, logPath, string.Format("File is not linked (you can try running Sublight application and link it with synchronized subtitle): {0}", file));
                        continue;
                    }
                    else
                    {
                        //is there any linked subtitle?
                        bool isLinked = false;
                        foreach (var subtitle in subtitles)
                        {
                            if (subtitle.IsLinked)
                            {
                                isLinked = true;
                                break;
                            }
                        }

                        if (!guessTitle && !isLinked)
                        {
                            showTipUseGuessTitleSwitch = true;
                            WriteLog(LogLevel.Tip, logPath, string.Format("There are no linked subtitles for this movie (please use Sublight): {0}", file));
                            continue;
                        }
                    }

                    resultSearching = Status.Success;
                    resultDownloading = Status.InProgress;
                    RefreshUI(left, top, resultHashing, resultSearching, resultDownloading, totalSubtitles);

                    try
                    {
                        bool appendLanguageCode;
                        
                        if (languages != null && languages.Length > 1) appendLanguageCode = true;
                        else if (alwaysAppendLanguageCode) appendLanguageCode = true;
                        else appendLanguageCode = false;

                        Encoding subtitleEncoding;
                        try
                        {
                            if (string.IsNullOrEmpty(encoding))
                            {
                                subtitleEncoding = Encoding.Default;
                            }
                            else
                            {
                                subtitleEncoding = Encoding.GetEncoding(encoding);
                            }
                        }
                        catch (Exception ex)
                        {
                            WriteLog(LogLevel.Error, logPath, string.Format("Parameter encoding ('{0}') is not valid: {1}", encoding, ex.Message));
                            subtitleEncoding = Encoding.Default;
                        }

                        //sort by language
                        List<Subtitle> subtitlesSorted = new List<Subtitle>();
                        foreach (string language in languages)
                        {
                            SubtitleLanguage sl = Sublight.Lib.Language.Util.GetLanguageByCode(language);
                            foreach (Subtitle subtitle in subtitles)
                            {
                                if (subtitle.Language == sl)
                                {
                                    subtitlesSorted.Add(subtitle);
                                }
                            }
                        }

                        if (subtitlesSorted.Count > 0 && reduceLanguageDownloads)
                        {
                            //download subtitle only for one language
                            subtitles = new[] { subtitlesSorted[0]};
                            WriteLog(LogLevel.Debug, logPath, string.Format("reduceLanguageDownloads: downloading only subtitle for language '{0}'", subtitlesSorted[0].Language));
                        }
                        else
                        {
                            subtitles = subtitlesSorted.ToArray();
                        }

                        WriteLog(LogLevel.Info, logPath, string.Format("Found {0} subtitles for file: {1}", subtitles.Length, file));
                        foreach (var subtitle in subtitles)
                        {
                            var sb = new StringBuilder();
                            sb.AppendFormat("{0} ({1})", subtitle.Title, subtitle.Year != null ? subtitle.Year.Value.ToString() : "N/A");
                            if (subtitle.Season != null && subtitle.Season.Value > 0)
                            {
                                sb.Append(", ");
                                sb.AppendFormat("S{0}", subtitle.Season.Value);
                            }
                            if (subtitle.Episode != null && subtitle.Episode.Value >= 0)
                            {
                                sb.AppendFormat("E{0}", subtitle.Episode.Value);
                            }
                            sb.AppendFormat(", language = {0}", Enum.GetName(typeof(SubtitleLanguage), subtitle.Language));
                            sb.AppendFormat(", id = {0}", subtitle.SubtitleID);

                            WriteLog(LogLevel.Debug, logPath, string.Format("   -> {0}", sb));
                        }

                        Sublight.Lib.Subtitle.Util.DownloadPausedHandler downloadPausedHandler =
                            delegate(string message, int seconds)
                                {
                                    totalSecondsPaused += seconds;
                                    if (seconds > 0)
                                    {
                                        smartDownload = true;
                                    }
                                    WriteLog(LogLevel.Info, logPath, message);
                                };

                        string[] downloadedSubtitles = Sublight.Lib.Subtitle.Util.DownloadSubtitles(session, file, subtitles, appendLanguageCode, null, subtitleEncoding.CodePage, downloadPausedHandler);
                        if (downloadedSubtitles != null)
                        {
                            bool isLinked = true;
                            foreach (var subtitle in subtitles)
                            {
                                if (!subtitle.IsLinked)
                                {
                                    isLinked = false;
                                    break;
                                }
                            }

                            if (!isLinked)
                            {
                                WriteLog(LogLevel.Tip, logPath, string.Format("Not all subtitles were linked (please use Sublight to confirm or report links): {0}", file));
                            }

                            totalSubtitles += downloadedSubtitles.Length;
                            WriteLog(LogLevel.Download, logPath, string.Format("{0}", file));

                            foreach (string downloadedSubtitle in downloadedSubtitles)
                            {
                                WriteLog(LogLevel.Download, logPath, string.Format("   -> {0}", downloadedSubtitle));
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        WriteLog(LogLevel.Error, logPath, ex.Message);

                        resultDownloading = Status.Failure;
                        RefreshUI(left, top, resultHashing, resultSearching, resultDownloading, totalSubtitles);
                        Thread.Sleep(1000);
                        continue;
                    }

                    resultDownloading = Status.Success;
                    RefreshUI(left, top, resultHashing, resultSearching, resultDownloading, totalSubtitles);
                }

                Console.WriteLine();

                if (totalSecondsPaused > 0)
                {
                    Console.WriteLine(string.Format("NOTE: You are anonymous user or you do not have enough points. Download was paused for {0} seconds.", totalSecondsPaused));
                    Console.WriteLine();
                }

                if (showTipUseGuessTitleSwitch)
                {
                    const string TIP_RUN_WITH_GUESS_TITLE_SWITCH = "You can use /guessTitle switch to download subtitles for movies which are not yet linked (there is possibility that you will download unsynchronized subtitle)";
                    WriteLog(LogLevel.Tip, logPath, TIP_RUN_WITH_GUESS_TITLE_SWITCH);
                    Console.WriteLine(string.Format("TIP: {0}", TIP_RUN_WITH_GUESS_TITLE_SWITCH));
                    Console.WriteLine();
                }

                Console.WriteLine("Please note: SublightCmd tries to automatically download most suitable subtitles for you. In case you are not satisfied with its results please use Sublight application where you can get much more subtitles (please add/vote links for synchronized subtitles and report unsubtitles subtitles).");

            }
            catch (Sublight.Lib.Types.Exception.QuotaReached)
            {
                const string msg = "Error: quota was reached. Your quota will be increased if you have positive Sublight points.";

                WriteLog(LogLevel.Error, logPath, msg, false);

                Environment.ExitCode = 1;
                Console.Error.WriteLine(msg);
            }
            catch (Exception ex)
            {
                WriteLog(LogLevel.Error, logPath, string.Format("Error: {0}", ex.Message), false);

                Environment.ExitCode = 1;
                Console.Error.WriteLine(string.Format("Error occured. {0}", ex.Message));
            }
            finally
            {
                if (session != Guid.Empty)
                {
                    try
                    {
                        Sublight.Lib.Session.Util.LogOut(session);
                    }
                    catch
                    {
                    }
                }

                if (_searchHistory != null)
                {
                    _searchHistory.Dispose();
                    _searchHistory = null;
                }

                if (waitForExit)
                {
                    Console.WriteLine();
                    Console.WriteLine("PRESS ENTER TO EXIT...");
                    Console.ReadLine();
                }
            }
        }

        /// <summary>
        /// Class for keeping search history. This enables faster scanning if SublightCmd
        /// is ran multiple times on same day and same files. This also keeps server less busy.
        /// </summary>
        class SearchHistory : IDisposable
        {
            private Dictionary<string, DateTime> _searchHistory;
            private readonly string _filePath;
            private const int CHECK_INTERVAL_DAYS = 1;

            public bool MovieRecentlySearched(string filename)
            {
                if (filename == null) return false;
                if (_searchHistory == null) return false;
                string filenameLC = filename.Trim().ToLower();
                if (_searchHistory.ContainsKey(filenameLC))
                {
                    DateTime previousSearchDate = _searchHistory[filenameLC];
                    if (previousSearchDate < DateTime.UtcNow.AddDays(-CHECK_INTERVAL_DAYS))
                    {
                        _searchHistory[filenameLC] = DateTime.UtcNow;
                        return false;
                    }
                    return true;
                }
                else
                {
                    _searchHistory.Add(filenameLC, DateTime.UtcNow);
                    return false;
                }
            }

            public SearchHistory()
            {
                _filePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
                _filePath = Path.Combine(_filePath, string.Format(@"SublightCmd\history.txt"));
                _searchHistory = new Dictionary<string, DateTime>();
                if (File.Exists(_filePath))
                {
                    using (StreamReader sr = new StreamReader(_filePath))
                    {
                        while (true)
                        {
                            string line = sr.ReadLine();
                            if (string.IsNullOrEmpty(line)) break;
                            int i = line.LastIndexOf('|');
                            if (i < 0) continue;
                            string filename = line.Substring(0, i).Trim().ToLower();
                            string date = line.Substring(i + 1).Trim();
                            try
                            {
                                DateTime dt = DateTime.ParseExact(date, "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture);
                                if (!_searchHistory.ContainsKey(filename))
                                {
                                    _searchHistory.Add(filename, dt);
                                }
                            }
                            catch (Exception)
                            {
                            }
                        }
                    }
                }
            }

            ~SearchHistory()
            {
                FlushToFile();
            }

            private void FlushToFile()
            {
                if (_searchHistory == null) return;
                lock (_searchHistory)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (KeyValuePair<string, DateTime> kvp in _searchHistory)
                    {
                        DateTime previousSearchDate = kvp.Value;
                        if (previousSearchDate < DateTime.UtcNow.AddDays(-CHECK_INTERVAL_DAYS))
                        {
                            //too old, we do not need to write this to file
                            continue;
                        }

                        sb.AppendFormat("{0}|{1}", kvp.Key, kvp.Value.ToString("yyyy-MM-dd"));
                        sb.AppendLine();
                    }


                    FileInfo fi = new FileInfo(_filePath);
                    DirectoryInfo di = fi.Directory;
                    if (!di.Exists)
                    {
                        di.Create();
                    }
                    File.WriteAllText(_filePath, sb.ToString());
                    _searchHistory = null;
                }
            }

            #region IDisposable Members

            public void Dispose()
            {
                FlushToFile();
            }

            #endregion
        }

        private static bool MovieRecentlySearched(string filename)
        {
            if (_searchHistory == null) throw new NullReferenceException("_searchHistory");
            return _searchHistory.MovieRecentlySearched(filename);
        }

        private static bool DetectSeasonEpisode(ref string detectedTitle, ref byte? detectedSeason, ref int? detectedEpisode)
        {
            try
            {
                Match match = Regex.Match(detectedTitle, SEASON_EPISODE_PATTERN_1);
                if (match.Success)
                {
                    if (DetectSeasonEpisode(match, ref detectedTitle, ref detectedSeason, ref detectedEpisode))
                    {
                        return true;
                    }
                }

                match = Regex.Match(detectedTitle, SEASON_EPISODE_PATTERN_2);
                if (match.Success)
                {
                    if (DetectSeasonEpisode(match, ref detectedTitle, ref detectedSeason, ref detectedEpisode))
                    {
                        return true;
                    }
                }

            }
            catch
            {
            }

            return false;
        }

        internal const string SEASON_EPISODE_PATTERN_1 = "\\b(?<season>[0-9]*?)[xX]{1,1}(?<episode>[0-9]*?)\\b"; //1x1
        internal const string SEASON_EPISODE_PATTERN_2 = "\\b[sS]{1,1}(?<season>[0-9]*?)[eE]{1,1}(?<episode>[0-9]*?)\\b";

        private static bool DetectSeasonEpisode(Match match, ref string detectedTitle, ref byte? detectedSeason, ref int? detectedEpisode)
        {
            if ((match == null) || (!match.Success))
            {
                return false;
            }

            int season;
            if (!Int32.TryParse(match.Groups["season"].Value, out season))
            {
                return false;
            }

            int episode;
            if (!Int32.TryParse(match.Groups["episode"].Value, out episode))
            {
                return false;
            }

            detectedTitle = detectedTitle.Substring(0, detectedTitle.IndexOf(match.Value)).Trim();
            detectedTitle = DeleteDuplicatedWhiteSpace(detectedTitle);

            detectedSeason = byte.Parse(season.ToString());
            detectedEpisode = int.Parse(episode.ToString());

            return true;
        }

        private static string DeleteDuplicatedWhiteSpace(string val)
        {
            if (String.IsNullOrEmpty(val))
            {
                return val;
            }

            try
            {
                string res = val;
                while (true)
                {
                    int i = res.IndexOf("  ");
                    if (i >= 0)
                    {
                        res = res.Substring(0, i) + res.Substring(i + 1);
                    }
                    else
                    {
                        return res;
                    }
                }
            }
            catch
            {
                return val;
            }
        }

        internal static bool DetectTitleSeasonEpisode(string filePath, out string detectedTitle, out byte? detectedSeason, out int? detectedEpisode)
        {
            detectedTitle = null;
            detectedSeason = null;
            detectedEpisode = null;

            try
            {
                const string strIgnoreWords = "HDTV DVD XOR AXXO XVID DIVX WS CAPH SEASON EPISODE ENG DVDRIP WS DSR NOTV DIMENSION PROPER R5 PUKKA AC3 MP3 AVI CAM TS DVDSCR SCREENER INTERNAL DIAMOND CD1 CD2 FXM FXG REPACK 720p RIP 720i 1080i 1080p HD MP4 BLU DISC MKV WMA WMV BRRIP BluRay BluRayRIP BDRIP";
                string[] ignoreWords = strIgnoreWords.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);

                string fileName;
                try
                {
                    fileName = Path.GetFileName(filePath);
                }
                catch
                {
                    fileName = filePath;
                }

                if (String.IsNullOrEmpty(fileName))
                {
                    return false;
                }

                fileName = fileName.Replace('.', ' ');
                fileName = fileName.Replace('[', ' ');
                fileName = fileName.Replace(']', ' ');
                fileName = fileName.Replace('-', ' ');
                fileName = fileName.Replace('(', ' ');
                fileName = fileName.Replace(')', ' ');
                fileName = fileName.Replace('_', ' ');
                fileName = fileName.Replace('\'', ' ');

                string[] fileNameSplits = fileName.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
                var searchWords = new List<string>();

                foreach (string fileNameSplit in fileNameSplits)
                {
                    bool ignore = false;
                    foreach (string ignoreWord in ignoreWords)
                    {
                        if (String.Compare(fileNameSplit, ignoreWord, true) == 0)
                        {
                            ignore = true;
                            break;
                        }
                    }

                    if (!ignore)
                    {
                        searchWords.Add(fileNameSplit);
                    }
                    else
                    {
                        break;
                    }
                }

                var sbSearchWords = new StringBuilder();
                for (int i = 0; i < searchWords.Count; i++)
                {
                    string tmp = searchWords[i].Trim();
                    if (Regex.IsMatch(tmp, "\\b[1,2][9,0][0-9][0-9]\\b"))
                    {
                        //year
                        continue;
                    }
                    if (tmp.Length <= 1)
                    {
                        //too short
                        continue;
                    }

                    sbSearchWords.Append(tmp);
                    if (i < (searchWords.Count - 1))
                    {
                        sbSearchWords.Append(' ');
                    }
                }
                detectedTitle = sbSearchWords.ToString();
                return DetectSeasonEpisode(ref detectedTitle, ref detectedSeason, ref detectedEpisode);
            }
            catch (Exception)
            {
                return false;
            }
        }

        private enum LogLevel : short
        {
            Debug = 25,
            Info = 50,
            Tip = 55,
            Download = 60,
            Warning = 75,
            Error = 100
        }

        private static bool WriteLog(LogLevel logLevel, string file, string message)
        {
            return WriteLog(logLevel, file, message, true);
        }

        private static bool WriteLog(LogLevel logLevel, string file, string message, bool writeToConsole)
        {
            if (Convert.ToInt16(logLevel) < (Convert.ToInt16(_applicationLogLevel)))
            {
                //we do not have to log this
                return true;
            }

            try
            {
                var sb = new StringBuilder();
                sb.AppendFormat("{0}: ", Enum.GetName(typeof (LogLevel), logLevel).ToUpper());
                sb.Append(message);
                sb.AppendLine();

                if (string.IsNullOrEmpty(file))
                {
                    if (writeToConsole)
                    {
                        Console.Write(sb.ToString());
                    }
                }
                else
                {
                    File.AppendAllText(file, sb.ToString());
                }

                return true;
            }
            catch
            {
                return false;
            }
        }

        private enum Status
        {
            Waiting,
            InProgress,
            Success,
            Failure,
            SubtitleNotFound,
            Skipped,
            Unknown
        }

        private static void RefreshUI(int left, int top, Status hashing, Status searching, Status downloading, int totalSubtitles)
        {
            int bufferSize = top + 50;
            if (bufferSize > Console.BufferHeight)
            {
                Console.BufferHeight = bufferSize;
            }

            ConsoleColor originalForeColor = Console.ForegroundColor;

            const string ACTION_HASHING = "Hashing";
            const string ACTION_SEARCH = "Searching";
            const string ACTION_DOWNLOADING = "Downloading";

            var actions = new[] { ACTION_HASHING, ACTION_SEARCH, ACTION_DOWNLOADING};
            int maxActionLen = 0;
            foreach (string action in actions)
            {
                if (action.Length > maxActionLen) maxActionLen = action.Length;
            }

            for (int i = 1; i <= actions.Length; i++)
            {
                string action = actions[i - 1];
                Console.SetCursorPosition(left, top + i);
                Console.Write(action);
                Console.Write(string.Format(" {0} ", new string('.', maxActionLen - action.Length + 5)));
                Status status = Status.Unknown;
                if (action == ACTION_HASHING) status = hashing;
                else if (action == ACTION_SEARCH) status = searching;
                else if (action == ACTION_DOWNLOADING) status = downloading;

                if (status == Status.Unknown) continue;

                Console.Write("[");
                string currentAction = string.Empty;
                if (status == Status.Waiting)
                {
                    Console.ForegroundColor = originalForeColor;
                    currentAction = "WAITING";
                }
                else if (status == Status.InProgress)
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    currentAction = "IN PROGRESS...";
                }
                else if (status == Status.Success)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    currentAction = "SUCCESS";
                }
                else if (status == Status.Failure)
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    currentAction = "FAILURE";
                }
                else if (status == Status.Skipped)
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    currentAction = "SKIPPED";
                }
                else if (status == Status.SubtitleNotFound)
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    currentAction = "SUBTITLE NOT FOUND";
                }

                Console.Write(currentAction);

                Console.ForegroundColor = originalForeColor;
                Console.Write("]");
                Console.Write(new string(' ', Console.BufferWidth - currentAction.Length - 20));
            }

            Console.SetCursorPosition(left, top + actions.Length + 1);
            Console.Write("".PadRight(Console.BufferWidth));

            Console.SetCursorPosition(left, top + actions.Length + 2);
            Console.Write(string.Format("Total subtitle downloads: {0}", totalSubtitles).PadRight(Console.BufferWidth));
        }
    }
}
